﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;

namespace Emilie.Animation
{
    /// <summary>
    /// A collection of useful extension methods for Storyboards and FrameworkElements
    /// </summary>
    public static class Extensions
    {
        #region Storyboard

        /// <summary>
        /// Allows you to await for a Storyboard to complete.
        /// Code From: http://www.sharpgis.net/post/2012/09/05/Running-a-Storyboard-as-a-Task.aspx
        /// </summary>
        /// <param name="storyboard">The storyboard to start</param>
        /// <returns>Await-able Task</returns>
        public static Task BeginAsync(this Storyboard storyboard)
        {
            System.Threading.Tasks.TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
            if (storyboard == null) tcs.SetException(new ArgumentNullException());
            else
            {
                EventHandler<object> onComplete = null;
                onComplete = (s, e) =>
                {
                    storyboard.Completed -= onComplete;
                    tcs.SetResult(true);
                };
                storyboard.Completed += onComplete;
                storyboard.Begin();
            }
            return tcs.Task;
        }




        /// <summary>
        /// Ensures the target has a valid Composite transform attached too it. If the element has an existing transform translate,
        /// transform offsets with be carried over. If the transform is a matrix or any other type, it will be overwritten.
        /// Does not need to be manually called if using animations inside Emilie.Animation.
        /// </summary>
        /// <param name="_target">Target element</param>
        public static CompositeTransform GetCompositeTransform(this FrameworkElement _target)
        {
            _target.EnsureCompositeTransform();
            return (CompositeTransform)_target.RenderTransform;
        }

        /// <summary>
        /// Ensures the target has a valid Plane Projection attached too it. Does not need to be called if using animations inside Emilie.Animation.
        /// </summary>
        /// <param name="target"></param>
        public static PlaneProjection GetPlaneProjection(this UIElement target)
        {
            if (target.Projection == null) target.Projection = new PlaneProjection();                   // Create a new target if none exists
            if (!(target.Projection is PlaneProjection)) target.Projection = new PlaneProjection();

            return (PlaneProjection)target.Projection;

            //try { PlaneProjection ct = _target.Projection as PlaneProjection; }                       // Ensure we can actually access the current Plane projection
            //catch { _target.Projection = new PlaneProjection(); }                                     // If we can't, overwrite it with a new blank one
        }

        public static void ApplyStoryboardOptions(this Storyboard storyboard, StoryboardOptions options)
        {
            if (options != null)
            {
                storyboard.AutoReverse = (options.AutoReverse.HasValue) && options.AutoReverse.Value;
                storyboard.SpeedRatio = (options.SpeedRatio.HasValue) ? options.SpeedRatio.Value : 1.0;
                if (options.RepeatForever.HasValue && options.RepeatForever.Value) storyboard.RepeatBehavior = new RepeatBehavior { Type = RepeatBehaviorType.Forever };
                else if (options.RepeatCount.HasValue) storyboard.RepeatBehavior = new RepeatBehavior { Count = options.RepeatCount.Value, Type = RepeatBehaviorType.Count };
            }
        }

        /// <summary>
        /// Registers the Storyboard as a resource of the current Window.Current.Content, and returns the unique string Key name of the resource
        /// </summary>
        /// <param name="storyboard"></param>
        /// <param name="resourceName"></param>
        /// <returns></returns>
        public static String AddToWindowResources(this Storyboard storyboard, String resourceName)
        {
            var e = Window.Current.Content as FrameworkElement;
            String ID = Base.GenerateUniqueID() + resourceName;                         // Generate a Unique ID for the storyboard
            e.Resources.Add(ID, storyboard);                                            // Register Storyboard with parent element
            return ID;
        }

        public static void AddExistingDoubleAnimation(this Storyboard storyboard, DoubleAnimation doubleAnimation, FrameworkElement target, String targetProperty)
        {
            Keyframes.EnsurePlumbing(target, targetProperty);
            Storyboard.SetTarget(doubleAnimation, target);
            Storyboard.SetTargetName(doubleAnimation, target.Name);
            Storyboard.SetTargetProperty(doubleAnimation, targetProperty);
            storyboard.Children.Add(doubleAnimation);
        }

        public static void AddDoubleAnimation(this Storyboard storyboard, FrameworkElement target, String targetProperty, Double? _from, Double? _to, TimeSpan? beginTime, TimeSpan _duration, EasingFunctionBase _easingMode)
        {
            Keyframes.EnsurePlumbing(target, targetProperty);                                                          

            DoubleAnimation tt = new DoubleAnimation
            {
                From = _from,
                To = _to,
                Duration = new Duration(_duration),
                EasingFunction = _easingMode,
                BeginTime = beginTime
            };

            storyboard.AddExistingDoubleAnimation(tt, target, targetProperty);
        }

        #endregion

        #region FrameworkElement

        /// <summary>
        /// Removes all active storyboards registered in the given FrameworkElement's resource dictionary.
        /// </summary>
        /// <param name="_target">FrameworkElement to remove storyboards from</param>
        public static void ClearLocalStoryboards(this FrameworkElement _target)
        {
            var resources = _target.Resources;
            List<object> keys = new List<object>();

            foreach (var r in resources)
            {
                if (r.Value.ToString() == "Windows.UI.Xaml.Media.Animation.Storyboard")
                {
                    Storyboard sb = _target.Resources[r.Key] as Storyboard;
                    sb.Stop();
                    sb = null;
                    keys.Add(r.Key);
                }
            }

            foreach (var key in keys) _target.Resources.Remove(key);
        }

        /// <summary>
        /// Sets the transform origins for a Framework Element. If there is no Composite transform, one will be created. Any existing transforms will be destroyed.
        /// </summary>
        /// <param name="_target">Target FrameworkElement</param>
        /// <param name="X">Centre X value (0.5 is middle)</param>
        /// <param name="Y">Centre Y value (0.5 is middle)</param>
        public static void SetCompositeTransformCentre(this FrameworkElement _target, double X, double Y)
        {
            var ct = _target.RenderTransform as CompositeTransform;
            if (ct == null)
            {
                _target.EnsureCompositeTransform();
                ct = _target.RenderTransform as CompositeTransform;
            }

            ct.CenterX = X;
            ct.CenterY = Y;
            _target.RenderTransformOrigin = new Windows.Foundation.Point(X, Y);
        }

        /// <summary>
        /// Ensures a framework element has a CompositeTransform. If one does not exist, one will be created. If any other transform exists, it will be overwritten.
        /// If using animation methods from this library, it is not necessary to call this manually.
        /// </summary>
        /// <param name="_target">Framework element to ensure has a composite transform</param>
        public static void EnsureCompositeTransform(this FrameworkElement _target)
        {
            var ct = _target.RenderTransform as CompositeTransform;                     // Check to see if we have a composite transform
            if (ct == null)                                                             // If we don't, create one.
            {
                var tt = _target.RenderTransform as TranslateTransform;                 // Check for a translate transform instead
                if (tt != null)                                                         // If we have have one, let's get the values out of it
                {
                    var x = tt.X; var y = tt.Y;                                         // Get old translate transform values
                    var nct = new CompositeTransform();                                 // Replace translate transform with composite transform
                    _target.RenderTransform = nct;                                      // Get it...
                    nct.TranslateX = x; nct.TranslateY = y;                             // Set the old tt values to the new ct
                }
                else _target.RenderTransform = new CompositeTransform();                // Create a new composite transform
                
                SetCompositeTransformCentre(_target, 0.5, 0.5);                         // Set the centre
            }
        }

        public static void EnsurePlaneProjection(this FrameworkElement _target)
        {
            _target.GetPlaneProjection();
        }

        #endregion

        public static Color? ConvertHexColorToColor(string color)
        {
            byte R;
            byte G;
            byte B;
            byte A;
            // SolidColorBrush scb;
            if (color.Length == 9)
            {
                A = System.Convert.ToByte(color.Substring(1, 2), 16);
                R = System.Convert.ToByte(color.Substring(3, 2), 16);
                G = System.Convert.ToByte(color.Substring(5, 2), 16);
                B = System.Convert.ToByte(color.Substring(7, 2), 16);
                return Color.FromArgb(A, R, G, B);
            }
            else if (color.Length == 7)
            {
                R = System.Convert.ToByte(color.Substring(1, 2), 16);
                G = System.Convert.ToByte(color.Substring(3, 2), 16);
                B = System.Convert.ToByte(color.Substring(5, 2), 16);
                return Color.FromArgb(255, R, G, B);
            }
            else return null;
        }

        public static void SetPoints(this KeySpline keySpline, Double x1, Double y1, Double x2, Double y2)
        {
            keySpline.ControlPoint1 = new Windows.Foundation.Point(x1, y1);
            keySpline.ControlPoint2 = new Windows.Foundation.Point(x2, y2);
        }
    }
}
